package fe;

import java.net.SocketException;
import java.util.Timer;
import java.util.TimerTask;
import java.net.UnknownHostException;
import udp.UdpServer;
import udp.UdpClient;
import udp.message.*;
import util.ConfigurationHelper;

public class FEUdp extends UdpServer {

	private UdpClient FeToSequencer;
	private UdpClient FeToReplicaManager;
	private int replyCounterCreate;
	private int replyCounterReserve;
	private int replyCounterReverveInter;
	private int replyCounterGetNon;
	private int replyCounterDuration;

	private final int FOURREPLY = 4;
	private final int THREEREPLY = 3;
	private Timer timer;

	private CreateAccountMessage createReply1;
	private CreateAccountMessage createReply2;
	private CreateAccountMessage createReply3;
	private CreateAccountMessage createReply4;

	private ReserveBookMessage reserveReply1;
	private ReserveBookMessage reserveReply2;
	private ReserveBookMessage reserveReply3;
	private ReserveBookMessage reserveReply4;

	private SetDurationMessage durationReply1;
	private SetDurationMessage durationReply2;
	private SetDurationMessage durationReply3;
	private SetDurationMessage durationReply4;

	private ReserveBookInterMessage reverveInterReply1;
	private ReserveBookInterMessage reverveInterReply2;
	private ReserveBookInterMessage reverveInterReply3;
	private ReserveBookInterMessage reverveInterReply4;

	private GetNonReturnersMessage getNonReply1;
	private GetNonReturnersMessage getNonReply2;
	private GetNonReturnersMessage getNonReply3;
	private GetNonReturnersMessage getNonReply4;

	private String result;
	private int errorReplica1 = 0;
	private int errorReplica2 = 0;
	private int errorReplica3 = 0;
	private int errorReplica4 = 0;

	public FEUdp(String id, int port) {
		super(id, port);
		replyCounterCreate = 0;
		replyCounterReserve = 0;
		replyCounterReverveInter = 0;
		replyCounterGetNon = 0;
		replyCounterDuration = 0;
		startUDPServer();
		timer = new Timer();

		try {
			FeToSequencer = new UdpClient(ConfigurationHelper.getProperty("sequencer.host"), ConfigurationHelper.getIntProperty("sequencer.port"));
			FeToReplicaManager = new UdpClient(ConfigurationHelper.getProperty("replica.manager.host"), ConfigurationHelper.getIntProperty("replica.manager.port"));
		} catch (SocketException e) {
			e.printStackTrace();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}

	}

	@Override
	// receive message from replicas
	public void receiveMessage(UdpMessage message) {
		
		// check of message are from library message
		if (message instanceof LibraryMessage) {
			LibraryMessage command = (LibraryMessage) message;

			// check message is from which command
			if (message instanceof CreateAccountMessage) {
				CreateAccountMessage commandResult = (CreateAccountMessage) command;
				setRepliesCreate(commandResult);
			} else if (message instanceof ReserveBookMessage) {
				ReserveBookMessage commandResult = (ReserveBookMessage) command;
				setRepliesReserve(commandResult);
			} else if (message instanceof GetNonReturnersMessage) {
				GetNonReturnersMessage commandResult = (GetNonReturnersMessage) command;
				setRepliesGetNonReturner(commandResult);
			} else if (message instanceof SetDurationMessage) {
				SetDurationMessage commandResult = (SetDurationMessage) command;
				setRepliesDuration(commandResult);
			} else if (message instanceof ReserveBookInterMessage) {
				ReserveBookInterMessage commandResult = (ReserveBookInterMessage) command;
				setRepliesReserveInter(commandResult);
			}
		}
	}

	public void sendToSequencer(UdpMessage message) {
		System.out.println("FEUdp level: sending message to sequencer");
		FeToSequencer.send(message);
		System.out.println("FEUdp level: DONE sending message to sequencer");
	}

	public void sendToReplicaManager(UdpMessage message) {
		System.out.println("FEUdp level: sending message to replica Manager");
		FeToReplicaManager.send(message);
		System.out
				.println("FEUdp level: DONE sending message to replica Manager");
	}

	public void setRepliesCreate(CreateAccountMessage message) {
		replyCounterCreate++;// TODO need to add semaphore here
		System.out.println(message.getResult());
		switch (replyCounterCreate) {
		case 1:
			createReply1 = message;
			timer.schedule(new ReceiveTimeout(this, "create"), 5000);
			break;
		case 2:
			createReply2 = message;
			break;
		case 3:
			createReply3 = message;
			break;
		case 4:
			createReply4 = message;
			break;
		default:
			System.out.println("Error Getting more than 4 replies");
			break;
		}
	}

	public void setRepliesReserve(ReserveBookMessage message) {
		replyCounterReserve++;// TODO need to add semaphore here
		System.out.println(message.getResult());
		switch (replyCounterReserve) {
		case 1:
			reserveReply1 = message;
			timer.schedule(new ReceiveTimeout(this, "reserve"), 5000);
			break;
		case 2:
			reserveReply2 = message;
			break;
		case 3:
			reserveReply3 = message;
			break;
		case 4:
			reserveReply4 = message;
			break;
		default:
			System.out.println("Error Getting more than 4 replies");
			break;
		}
	}

	public void setRepliesDuration(SetDurationMessage message) {
		replyCounterDuration++;// TODO need to add semaphore here
		System.out.println(message.getResult());
		switch (replyCounterDuration) {
		case 1:
			durationReply1 = message;
			timer.schedule(new ReceiveTimeout(this, "duration"), 5000);
			break;
		case 2:
			durationReply2 = message;
			
			break;
		case 3:
			durationReply3 = message;
			break;
		case 4:
			durationReply4 = message;
			break;
		default:
			System.out.println("Error Getting more than 4 replies");
			break;
		}
	}

	public void setRepliesReserveInter(ReserveBookInterMessage message) {
		replyCounterReverveInter++;// TODO need to add semaphore here
		System.out.println(message.getResult());
		switch (replyCounterReverveInter) {
		case 1:
			reverveInterReply1 = message;
			timer.schedule(new ReceiveTimeout(this, "reserveInter"), 5000);
			break;
		case 2:
			reverveInterReply2 = message;

			break;
		case 3:
			reverveInterReply3 = message;
			break;
		case 4:
			reverveInterReply4 = message;
			break;
		default:
			System.out.println("Error Getting more than 4 replies");
			break;
		}
	}

	public void setRepliesGetNonReturner(GetNonReturnersMessage message) {
		replyCounterGetNon++;// TODO need to add semaphore here
		System.out.println(message.getResult());
		switch (replyCounterGetNon) {
		case 1:
			getNonReply1 = message;
			timer.schedule(new ReceiveTimeout(this, "getnon"), 5000);
			break;
		case 2:
			getNonReply2 = message;

			break;
		case 3:
			getNonReply3 = message;
			break;
		case 4:
			getNonReply4 = message;
			break;
		default:
			System.out.println("Error Getting more than 4 replies");
			break;
		}
	}

	public void setResultCreate() {

		if (replyCounterCreate == FOURREPLY) {

			// reset counter and replies messages;

			// if all four is equal
			if (createReply1.getResult().equals(createReply2.getResult())
					&& createReply2.getResult()
							.equals(createReply3.getResult())
					&& createReply3.getResult()
							.equals(createReply4.getResult()))
				result = createReply1.getResult();

			else
				result = "four replies but errors";

		}

		else if (replyCounterCreate == THREEREPLY) {

			// if all three are equal
			if (createReply1.getResult().equals(createReply2.getResult())
					&& createReply2.getResult()
							.equals(createReply3.getResult()))
				result = createReply1.getResult();

			// if 1 and 2 are equal
			else if (createReply1.getResult().equals(createReply2.getResult())) {
				errorCounter(createReply3);
				result = createReply1.getResult();
			}
			// if 1 and 3 are equal
			else if (createReply1.getResult().equals(createReply3.getResult())) {
				errorCounter(createReply2);
				result = createReply1.getResult();
			}
			// if 2 and 3 are equal
			else if (createReply2.getResult().equals(createReply3.getResult())) {
				errorCounter(createReply1);
				result = createReply2.getResult();
			} else
				result = "three replies but none of them matches";
		} else
			result = "Something went wrong";
	
		resetCreate();
	}

	public void setResultReserve() {

		if (replyCounterReserve == FOURREPLY) {

			// reset counter and replies messages;

			// if all four is equal
			if (reserveReply1.getResult().equals(reserveReply2.getResult())
					&& reserveReply2.getResult().equals(
							reserveReply3.getResult())
					&& reserveReply3.getResult().equals(
							reserveReply4.getResult()))
				result = reserveReply1.getResult();

			else
				result = "four replies but errors";

		}

		else if (replyCounterReserve == THREEREPLY) {

			// if all three are equal
			if (reserveReply1.getResult().equals(reserveReply2.getResult())
					&& reserveReply2.getResult().equals(
							reserveReply3.getResult()))
				result = reserveReply1.getResult();

			// if 1 and 2 are equal
			else if (reserveReply1.getResult()
					.equals(reserveReply2.getResult())) {
				errorCounter(reserveReply3);
				result = reserveReply1.getResult();
			}
			// if 1 and 3 are equal
			else if (reserveReply1.getResult()
					.equals(reserveReply3.getResult())) {
				errorCounter(reserveReply2);
				result = reserveReply1.getResult();
			}
			// if 2 and 3 are equal
			else if (reserveReply2.getResult()
					.equals(reserveReply3.getResult())) {
				errorCounter(reserveReply1);
				result = reserveReply2.getResult();
			} else
				result = "three replies but none of them matches";
		} else
			result = "Something went wrong";

		resetReserve();
	}

	public void setResultDuration() {

		if (replyCounterDuration == FOURREPLY) {

			// reset counter and replies messages;

			// if all four is equal
			if (durationReply1.getResult().equals(durationReply2.getResult())
					&& durationReply2.getResult().equals(
							durationReply3.getResult())
					&& durationReply3.getResult().equals(
							durationReply4.getResult()))
				result = durationReply1.getResult();

			else
				result = "four replies but errors";

		}

		else if (replyCounterDuration == THREEREPLY) {

			// if all three are equal
			if (durationReply1.getResult().equals(durationReply2.getResult())
					&& durationReply2.getResult().equals(
							durationReply3.getResult()))
				result = durationReply1.getResult();

			// if 1 and 2 are equal
			else if (durationReply1.getResult().equals(
					durationReply2.getResult())) {
				errorCounter(durationReply3);
				result = durationReply1.getResult();
			}
			// if 1 and 3 are equal
			else if (durationReply1.getResult().equals(
					durationReply3.getResult())) {
				errorCounter(durationReply2);
				result = durationReply1.getResult();
			}
			// if 2 and 3 are equal
			else if (durationReply2.getResult().equals(
					durationReply3.getResult())) {
				errorCounter(durationReply1);
				result = durationReply2.getResult();
			} else
				result = "three replies but none of them matches";
		} else
			result = "Something went wrong";

		resetDuration();
	}

	public void setResultReverveInter() {

		if (replyCounterReverveInter == FOURREPLY) {

			// reset counter and replies messages;

			// if all four is equal
			if (reverveInterReply1.getResult().equals(
					reverveInterReply2.getResult())
					&& reverveInterReply2.getResult().equals(
							reverveInterReply3.getResult())
					&& reverveInterReply3.getResult().equals(
							reverveInterReply4.getResult()))
				result = reverveInterReply1.getResult();

			else
				result = "four replies but errors";

		}

		else if (replyCounterReverveInter == THREEREPLY) {

			// if all three are equal
			if (reverveInterReply1.getResult().equals(
					reverveInterReply2.getResult())
					&& reverveInterReply2.getResult().equals(
							reverveInterReply3.getResult()))
				result = reverveInterReply1.getResult();

			// if 1 and 2 are equal
			else if (reverveInterReply1.getResult().equals(
					reverveInterReply2.getResult())) {
				errorCounter(reverveInterReply3);
				result = reverveInterReply1.getResult();
			}
			// if 1 and 3 are equal
			else if (reverveInterReply1.getResult().equals(
					reverveInterReply3.getResult())) {
				errorCounter(reverveInterReply2);
				result = reverveInterReply1.getResult();
			}
			// if 2 and 3 are equal
			else if (reverveInterReply2.getResult().equals(
					reverveInterReply3.getResult())) {
				errorCounter(reverveInterReply1);
				result = reverveInterReply2.getResult();
			} else
				result = "three replies but none of them matches";
		} else
			result = "Something went wrong";

		resetReserveInter();
	}

	public void setResultGetNon() {

		if (replyCounterGetNon == FOURREPLY) {

			// reset counter and replies messages;

			// if all four is equal
			if (getNonReply1.getResult().equals(getNonReply2.getResult())
					&& getNonReply2.getResult()
							.equals(getNonReply3.getResult())
					&& getNonReply3.getResult()
							.equals(getNonReply4.getResult()))
				result = getNonReply1.getResult();

			else
				result = "four replies but errors";

		}

		else if (replyCounterGetNon == THREEREPLY) {

			// if all three are equal
			if (getNonReply1.getResult().equals(getNonReply2.getResult())
					&& getNonReply2.getResult()
							.equals(getNonReply3.getResult()))
				result = reverveInterReply1.getResult();

			// if 1 and 2 are equal
			else if (getNonReply1.getResult().equals(getNonReply2.getResult())) {
				errorCounter(getNonReply3);
				result = getNonReply1.getResult();
			}
			// if 1 and 3 are equal
			else if (getNonReply1.getResult().equals(getNonReply3.getResult())) {
				errorCounter(getNonReply2);
				result = getNonReply1.getResult();
			}
			// if 2 and 3 are equal
			else if (getNonReply2.getResult().equals(getNonReply3.getResult())) {
				errorCounter(getNonReply1);
				result = getNonReply2.getResult();
			} else
				result = "three replies but none of them matches";
		} else
			result = "Something went wrong";

		resetGetNon();
	}

	public void errorCounter(UdpMessage message) {
		System.out.println("Sending error message to RM");
		String replicaid="";
		if (message instanceof LibraryMessage) {
			LibraryMessage command = (LibraryMessage) message;

			// check message is from which command
			if (message instanceof CreateAccountMessage) {
				CreateAccountMessage commandResult = (CreateAccountMessage) command;
				replicaid = commandResult.getReplicaId();
			} else if (message instanceof ReserveBookMessage) {
				ReserveBookMessage commandResult = (ReserveBookMessage) command;
				replicaid = commandResult.getReplicaId();
			} else if (message instanceof GetNonReturnersMessage) {
				GetNonReturnersMessage commandResult = (GetNonReturnersMessage) command;
				replicaid = commandResult.getReplicaId();
			} else if (message instanceof SetDurationMessage) {
				SetDurationMessage commandResult = (SetDurationMessage) command;
				replicaid = commandResult.getReplicaId();
			} else if (message instanceof ReserveBookInterMessage) {
				ReserveBookInterMessage commandResult = (ReserveBookInterMessage) command;
				replicaid = commandResult.getReplicaId();
			}
		}
		ReportReplicaMessage errorMessage = new ReportReplicaMessage(ConfigurationHelper.getProperty("sequencer.host"),replicaid);
		FeToReplicaManager.send(errorMessage);

	}

	public void resetCreate() {
		this.replyCounterCreate = 0;

	}

	public void resetReserve() {
		this.replyCounterReserve = 0;

	}

	public void resetReserveInter() {
		this.replyCounterReverveInter = 0;

	}

	public void resetGetNon() {
		this.replyCounterGetNon = 0;

	}

	public void resetDuration() {
		this.replyCounterDuration = 0;

	}
	
	public void onTimeout(String action) {
		// Time has passed since sendToSequencer
		switch(action) {
		case "create":
			setResultCreate();
			break;
		case "reserve":
			setResultReserve();
			break;
		case "reserveInter":
			setResultReserve();
			break;
		case "duration":
			setResultDuration();
			break;
		case "getnon":
			setResultGetNon();
			break;
		default:
			System.out.println("bad timeout action name");
			
		}
	}
	

	public String getResult() {
		String returnResult = result;
		if (result != null) result = null;
		return returnResult;
	}
	
	class ReceiveTimeout extends TimerTask {
		private FEUdp feudp;
		private String action;
		ReceiveTimeout(FEUdp feudp, String action) {
			this.feudp = feudp;
			this.action = action;
		}
		public void run() {
			feudp.onTimeout(action);
		}
	}
	

}
